//**********************************************************
// Object
//**********************************************************
Object.getType = function(instance)
{
	var ctor = instance.constructor;
	if(!ctor || (typeof (ctor) !== "function") || !ctor.__typeName || (ctor.__typeName === 'Object'))
		return Object;
	return ctor;
}
Object.getTypeName = function(instance)
{
	return Object.getType(instance).getName();
}
Object.Null = Object.Null; // Undefined value for passing into .NET code as null object. Normal javasctipt null is passed as System.DBNull.


//**********************************************************
// Function
//**********************************************************
Function.createCallback = function(method, context)
{
	return function(){method(context);}
}
Function.createDelegate = function(instance, method)
{
	return function(){method.apply(instance, arguments);}
}


//**********************************************************
// Type
//**********************************************************
Type = Function;
Type.__registeredTypes = {};
Type.__registerType = function(type, typeName, baseType)
{
	if(Type.__registeredTypes[typeName])
		return;

	this.__typeName = typeName
	if(baseType)
		this.__baseType = baseType;

	Type.__registeredTypes[typeName] = type;
	return this;
}

Type.prototype.registerType = function(typeName, baseType)
{
	var type = eval(typeName);
	return Type.__registerType.call(type, type, typeName, baseType);
}

Type.prototype.getBaseType = function()
{
	return (typeof (this.__baseType) === "undefined") ? null : this.__baseType;
}

Type.prototype.getName = function()
{
	return (typeof (this.__typeName) === "undefined") ? "" : this.__typeName;
}

Type.prototype.inheritsFrom = function Type$inheritsFrom(parentType)
{
	var baseType = this.__baseType;
	while(baseType)
	{
		if(baseType === parentType)
			return true;
		baseType = baseType.__baseType;
	}
	return false;
}

Type.prototype.isImplementedBy = function(instance)
{
	if(typeof (instance) === "undefined" || instance === null) return false;
	var instanceType = Object.getType(instance);
	return !!(instanceType.implementsInterface && instanceType.implementsInterface(this));
}

Type.prototype.implementsInterface = function(interfaceType)
{
	return false;
}

Type.prototype.isInstanceOfType = function(instance)
{
	if(typeof (instance) === "undefined" || instance === null) return false;
	if(instance instanceof this) return true;
	var instanceType = Object.getType(instance);
	return !!(instanceType === this) ||
		(instanceType.inheritsFrom && instanceType.inheritsFrom(this)) ||
		(instanceType.implementsInterface && instanceType.implementsInterface(this));
}
Type.prototype.exportTypes = function(windowObj)
{
	for(typeName in Type.__registeredTypes)
		windowObj[typeName] = Type.__registeredTypes[typeName];
}

Type.registerType('Array');
Type.registerType('Boolean');
Type.registerType('Date');
Type.registerType('Enumerator');
Type.registerType('Function');
Type.registerType('Number');
Type.registerType('Object');
Type.registerType('RegExp');
Type.registerType('String');
Type.registerType('Type');


//**********************************************************
// Boolean
//**********************************************************
Boolean.parse = function(value)
{
	return typeof(value) == "string" ? (value.toLowerCase() == "true") : !!value;
}



//**********************************************************
// String
//**********************************************************
String.prototype.trim = function()
{
    return this.replace(/(^\s*)|(\s*$)/g, "");
}

String.prototype.replaceAll = function(pattern, text, ignoreCase)
{
	if(RegExp.prototype.isPrototypeOf(pattern))
		throw new Error("Not supported. Use \"g\" flag with regExp patterns.");

	return this.replace(new RegExp(pattern, (ignoreCase === true ? "gi" : "g")), text);
}

String.prototype.left = function(len)
{
	return this.substr(0, len);
}

String.prototype.right = function(len)
{
	if(this.length <= len)
		return this;

	return this.substr(this.length - len);
}

String.prototype.padl = function(len, character)
{
	return (String.string(len, character) + this.substr(0, len)).right(len);
}

String.prototype.padr = function(len, character)
{
	return (this.substr(0, len) + String.string(len, character)).left(len);
}

String.prototype.substringBefore = function(character)
{
	var index = this.indexOf(character);
	if(index == -1)
		return "";

	return this.substr(0, index);
}

String.prototype.substringAfter = function(character)
{
	var index = this.indexOf(character);
	if(index == -1)
		return "";

	return this.substr(index + 1);
}

String._spaces = "";
String.spaces = function(len)
{
	while(String._spaces.length < len)
		String._spaces += " ";
	return String._spaces.substr(0, len);		
}

String.string = function(number, character)
{
	if(number <= 0) return "";
	if(!character || character == " ")
		return String.spaces(number);
	else
		return String.spaces(number).replace(/ /g, character);
}

String.right = function(str, len)
{
	if(str.length <= len)
		return str;

	return str.substr(str.length - len);
}

String.substringBefore = function(str, character)
{
	if(!str)
		return "";

	var index = str.indexOf(character);
	if(index == -1)
		return "";

	return str.substr(0, index);
}

String.substringAfter = function(str, character)
{
	if(!str)
		return "";

	var index = str.indexOf(character);
	if(index == -1)
		return "";

	return str.substr(index + 1);
}

//**********************************************************
// Date
//**********************************************************
Date.prototype.format = function(formatString)
{
	return Date.format(this, formatString);
}

Date.prototype.add = function(timeSpan)
{
	return new Date(this.valueOf() + timeSpan.valueOf());
}

Date.prototype.subtract = function(timeSpan)
{
	return new Date(this.valueOf() - timeSpan.valueOf());
}

Date._formatter = null;
Date.format = function(date, formatString)
{
	if(!date || isNaN(date)) return "";
	if(!formatString) return "";
	if(!Date._formatter)
	{
		var host = window.top.osh.getHost();
		Date._formatter = host.Services.GetService("Osh.Formatter");
	}
	return Date._formatter.FormatDateTime(date.getVarDate(), formatString, null);
}

Date.now = function()
{
	return new Date();
}

Date.utcNow = function()
{
	var now = new Date();
	return new Date(now.valueOf() + now.getTimezoneOffset() * 60 * 1000);
}

Date.equals = function(date1, date2)
{
	if(date1 == null && date2 == null)
		return true;
	if(date1 == null || date2 == null)
		return false;
	return date1.valueOf() == date2.valueOf();
}

Date.notEquals = function(date1, date2)
{
	return !Date.equals(date1, date2);
}

Date.min = function(date1, date2)
{
	return new Date(Math.min(date1.valueOf(), date2.valueOf()));
}

Date.max = function(date1, date2)
{
	return new Date(Math.max(date1.valueOf(), date2.valueOf()));
}

Date.calcProgress = function(startTime, endTime, positionTime)
{
	if(!startTime || !endTime || !positionTime)
		return 0;
	
	if(positionTime <= startTime)
		return 0;
	else if(positionTime >= endTime)
		return 100;
	else
	{
		var totalMs = (endTime.valueOf() - startTime.valueOf());
		if(totalMs > 0)
			return parseInt(Math.round((positionTime.valueOf() - startTime.valueOf()) * 1000 / totalMs)) / 10;
		else
			return 0;
	}
}


//**********************************************************
// Enumerator
//**********************************************************
Enumerator.prototype.toArray = function()
{
	var array = [];
	for(; !this.atEnd(); this.moveNext())
		array.push(this.item());
	return array;
}


//**********************************************************
// Point
//**********************************************************
function Point(x, y)
{
	this.x = x;
	this.y = y;
}
Type.registerType('Point');


//**********************************************************
// Rect
//**********************************************************
function Rect(left, top, width, height)
{
	this.left = parseInt(left);
	this.top = parseInt(top);
	this.width = parseInt(width);
	this.height = parseInt(height);
}

Rect.prototype.toString = function()
{
	return "Rect(" + this.left + ", " + this.top + ", " + this.width + ", " + this.height + ")";
}
Type.registerType('Rect');


//**********************************************************
// Event
//**********************************************************
function Event(debug)
{
	this._objects = new Array();
	this._methods = new Array();
	this._debug = debug;
}

Event.prototype.attach = function(obj, func)
{
	if(!func)
		throw new Error("Argument 'func' is null.");

	if(this.isAttached(obj, func))
		return;
		
	this._objects.push(obj);
	this._methods.push(func);
}

Event.prototype.detach = function(obj, func)
{
	if(!func)
		throw new Error("Argument 'func' is null.");

	for(var i = 0; i < this._objects.length; i++)
	{
		if(this._objects[i] == obj && this._methods[i] == func)
		{
			this._objects.splice(i, 1);
			this._methods.splice(i, 1);
			return;
		}
	}
}

Event.prototype.isAttached = function(obj, func)
{
	for(var i = 0; i < this._objects.length; i++)
	{
		if(this._objects[i] == obj && this._methods[i] == func)
			return true;
	}
	return false;
}

Event.prototype.hasHandlers = function()
{
	return this._objects.length > 0;
}

Event.prototype.fire = function(sender, args)
{
	if(this._objects.length == 0)
		return;
		
	for(var i = 0; i < this._objects.length; i++)
	{
		try
		{
			if(typeof(this._methods[i]) == "function")
				this._methods[i].call(this._objects[i], sender, args);
			else
				eval(this._methods[i]);
		}
		catch(e)
		{
			if(e.number == -2146823277 ||    // Can't execute code from a freed script.
				e.number == -2147418113)     // Unexpected call to method or property access.
			{
				this._objects.splice(i, 1);
				this._methods.splice(i, 1);
				i--;
			}
			else
			{
				//alert(window.top.log.dump(e));
				throw e;
			}
		}
	}
}
Type.registerType('Event');


//**********************************************************
// EventSink
//**********************************************************
function EventSink(obj, eventName, callbackMethod)
{
	var _host = window.top.OSH.getHost();
	var _typeFullName = _host.GetTypeFullName(obj);
	var _eventHandlerName = generateEventHandlerName();
	var _eventSource = _host.AttachEvent(obj, eventName, createEventCallback());
	
	function createEventCallback()
	{
		if(EventSink.events == null)
		{
			window.top.OSH.getHost().DetachAllEvents();
			EventSink.events = {};
		}
		EventSink.events[_eventHandlerName] = onEvent;
		var callbackCode = "EventSink.events." + _eventHandlerName + "()";
		return callbackCode;
	}
	
	function generateEventHandlerName()
	{
		return _typeFullName.replace(/\./g, "_") + "_" + eventName + "_" + getGlobalEventCounter();
	}
	
	function getGlobalEventCounter()
	{
		if(EventSink._counter == null)
			EventSink._counter = 0;
		return EventSink._counter++;
	}
	
	function onEvent()
	{
//		try
//		{
			callbackMethod(_eventSource.EventSender, _eventSource.EventArgs);
//		}
//		catch(e)
//		{
//			window.top.log.exception(e, "Error in event handler " + _eventHandlerName + ".");
//		}
	}
	
	this.detach = function()
	{
		_eventSource.Detach();
	}
}
Type.registerType('EventSink');


//**********************************************************
// CommandHandler
//**********************************************************
function CommandHandler(owner)
{
	this._owner = owner;
	this._children = null;
}

CommandHandler.prototype.refresh = function()
{
	this._children = null;
}

CommandHandler.prototype.processKey = function(sender, event)
{
	if(event.processed) return;
	
	// Find an active child and call its processKey method
	var childFound = false;
	var children = this.getAccessibleChildren();
	for(var i = 0; i < children.length; i++)
	{
		var child = children[i];
		if(child.selected != null && child.selected)
		{
			childFound = true;
			child.processKey(this._owner, event);
			break;
		}
	}
	
	if(children.length > 0 && !childFound)
	{
		window.top.log.writeln("Warning! Can't find a selected control to pass the key.");
	}
}

CommandHandler.prototype.getAccessibleChildren = function()
{
	if(this._children == null)
	{
		this._children = new Array();
		if(Screen.prototype.isPrototypeOf(this._owner))
		{
			// It has to be the current screen.
			var doc = this._owner.getDocument();
			if(doc != null && doc.body != null)
				this.collectAccessibleChildren(doc.body);
		}
		else if(this._owner.children != null)
		{
			this.collectAccessibleChildren(this._owner);
		}
	}
	return this._children;
}

CommandHandler.prototype.collectAccessibleChildren = function(element)
{
	var children = element.children;
	if(children == null) return;

	for(var i = 0; i < children.length; i++)
	{
		var child = children.item(i);
		if(typeof(child.processKey) == "unknown")	// It's a behavior's method
		{
			this._children.push(child);
		}
		else
			this.collectAccessibleChildren(child);
	}
}
Type.registerType('CommandHandler');


//**********************************************************
// Timer
//**********************************************************
function Timer(name, interval)
{
	this._name = name;
	this._interval = (!interval ? 0 : interval.valueOf());
	this._autoReset = true;
	this._timeoutId = null;
	this._shared = false;
	this._startCount = 0;
	this.onTimeout = new Event();
}

Timer.prototype.getName = function()
{
	return this._name;
}

Timer.prototype.setName = function(value)
{
	this._name = value;
}

Timer.prototype.getShared = function()
{
	return this._shared;
}

Timer.prototype.setShared = function(value)
{
	this._shared = value;
	if(!this._shared)
		this._startCount = 0;
}
Timer.prototype.getAutoReset = function()
{
	return this._autoReset;
}

Timer.prototype.setAutoReset = function(value)
{
	if(this._autoReset == value)
		return;
	
	if(this.getEnabled())
	{
		this.stop();
		this._autoReset = value;
		this.start();
	}
	else
		this._autoReset = value;
}

Timer.prototype.getInterval = function()
{
	return this._interval;
}

Timer.prototype.setInterval = function(interval)
{
	var newInterval = !interval ? 0 : interval.valueOf();
	if(newInterval == this._interval) return;

	this._interval = newInterval;
	if(this.getEnabled())
	{
		this.stop();
		this.start();
	}
}

Timer.prototype.getEnabled = function()
{
	return !!this._timeoutId;
}

Timer.prototype.setEnabled = function(value)
{
	if(value)
		this.start();
	else
		this.stop();
}

Timer.prototype.start = function()
{
	if(Timer._shouldDebug(this))
		window.top.log.writeln(this._name + ".start() : " + this._timeoutId + " : " + this._startCount + " : " + this._interval);

	if(this._interval == 0)
		return;

	if(this._shared)	
		this._startCount++;

	if(!this._timeoutId)
	{	
		if(this._autoReset)
			this._timeoutId = window.top.setInterval(Timer._createOnTimeoutClosure(this), this._interval);
		else
			this._timeoutId = window.top.setTimeout(Timer._createOnTimeoutClosure(this), this._interval);
	}
	
	if(Timer._shouldDebug(this))
		window.top.log.writeln(this._name + ".id = " + this._timeoutId);
}

Timer.prototype.stop = function()
{
	if(Timer._shouldDebug(this))
		window.top.log.writeln(this._name + ".stop() : " + this._timeoutId + " : " + this._startCount + " :" + this._interval);

	if(!this._timeoutId)
		return;

	if(this._shared)
		if(this._startCount > 0)
			this._startCount--;

	if(this._startCount == 0)
	{
		if(this._autoReset)
			window.top.clearInterval(this._timeoutId);
		else
			window.top.clearTimeout(this._timeoutId);

		this._timeoutId = null;
	}
	
}

Timer.prototype.restart = function()
{
	if(this.getEnabled())
		this.stop();
	this.start();
}

Timer.prototype._onTimeout = function()
{
	var debugging = Timer._shouldDebug(this);
	if(debugging)
		window.top.log.writeln(this._name + ".onTimeout() start");

	if(!this._autoReset)
	{
		window.top.clearTimeout(this._timeoutId);
		this._timeoutId = null;
	}

	var e = window.top.document.createEventObject();
	this.onTimeout.fire(this, e);

	if(debugging)
		window.top.log.writeln(this._name + ".onTimeout() end");
}

Timer._createOnTimeoutClosure = function(timer)
{
	return (function(){ timer._onTimeout(); });
}

Timer._debugging = false;
Timer._debugTimerNames = null;
Timer.startDebugging = function(timerNames)
{
	Timer._debugTimerNames = timerNames;
	Timer._debugging = true;
}
Timer.stopDebugging = function()
{
	Timer._debugging = false;
}
Timer.isDebugging = function()
{
	return Timer._debugging;
}
Timer._shouldDebug = function(timer)
{
	if(!Timer._debugging)
		return false;
	return !Timer._debugTimerNames || Timer._debugTimerNames.indexOf(timer._name) >= 0;
}
Type.registerType('Timer');


//**********************************************************
// FrameController
//**********************************************************
function FrameController(frameElement, agent, host)
{
	var _document;
	
	function initialize()
	{
		frameElement.onreadystatechange = onFrameReadyStateChange;
	}
				
	function onFrameReadyStateChange()
	{
		if(frameElement.readyState == "loading")
		{
			_document = window.top.OSH.getHost().GetFrameDocument(frameElement);
			if(_document.URL == "about:blank")
			{
				_document = null;
				return;
			}
			
			_document.parentWindow.host = host;
			
			agent = agent.toString().substring(8); // remove 'function' keyword
			agent = "new function" + agent.substring(agent.indexOf("(")) + "(window.host);";
			_document.parentWindow.execScript(agent);

			_document.parentWindow.host = null;
		}
	}
	
	this.navigate = function(url)
	{
		_document = null;
		frameElement.src = url;
	}
	
	this.getDocument = function()
	{
		return _document;
	}
	
	this.getFrameElement = function()
	{
		return frameElement;
	}

	initialize();
}
Type.registerType('FrameController');


//**********************************************************
// Keys
//**********************************************************
function Keys() {}
Keys.VK_BACK                 = 0x08;
Keys.VK_TAB                  = 0x09;
Keys.VK_CLEAR                = 0x0C;
Keys.VK_RETURN               = 0x0D;
Keys.VK_SHIFT                = 0x10;
Keys.VK_CONTROL              = 0x11;
Keys.VK_MENU                 = 0x12;
Keys.VK_PAUSE                = 0x13;
Keys.VK_CAPITAL              = 0x14;
Keys.VK_ESCAPE               = 0x1B;
Keys.VK_SPACE                = 0x20;
Keys.VK_PRIOR                = 0x21;
Keys.VK_NEXT                 = 0x22;
Keys.VK_END                  = 0x23;
Keys.VK_HOME                 = 0x24;
Keys.VK_LEFT                 = 0x25;
Keys.VK_UP                   = 0x26;
Keys.VK_RIGHT                = 0x27;
Keys.VK_DOWN                 = 0x28;
Keys.VK_SELECT               = 0x29;
Keys.VK_PRINT                = 0x2A;
Keys.VK_EXECUTE              = 0x2B;
Keys.VK_SNAPSHOT             = 0x2C;
Keys.VK_INSERT               = 0x2D;
Keys.VK_DELETE               = 0x2E;
Keys.VK_HELP                 = 0x2F;
Keys.VK_0                    = 0x30;
Keys.VK_1                    = 0x31;
Keys.VK_2                    = 0x32;
Keys.VK_3                    = 0x33;
Keys.VK_4                    = 0x34;
Keys.VK_5                    = 0x35;
Keys.VK_6                    = 0x36;
Keys.VK_7                    = 0x37;
Keys.VK_8                    = 0x38;
Keys.VK_9                    = 0x39;

Keys.VK_BROWSER_BACK         = 0xA6;
Keys.VK_BROWSER_FORWARD      = 0xA7;
Keys.VK_BROWSER_REFRESH      = 0xA8;
Keys.VK_BROWSER_STOP         = 0xA9;
Keys.VK_BROWSER_SEARCH       = 0xAA;
Keys.VK_BROWSER_FAVORITES    = 0xAB;
Keys.VK_BROWSER_HOME         = 0xAC;

Keys.VK_VOLUME_MUTE          = 0xAD;
Keys.VK_VOLUME_DOWN          = 0xAE;
Keys.VK_VOLUME_UP            = 0xAF;

Keys.VK_MEDIA_NEXT_TRACK     = 0xB0;
Keys.VK_MEDIA_PREV_TRACK     = 0xB1;
Keys.VK_MEDIA_STOP           = 0xB2;
Keys.VK_MEDIA_PLAY_PAUSE     = 0xB3;

Keys.VK_PLAY                 = 0xFA; // MC code
Keys.VK_ZOOM                 = 0xFB;

// Alternative key names
Keys.VK_OK                   = Keys.VK_RETURN;
Keys.VK_ENTER                = Keys.VK_RETURN;
Keys.VK_PAGEUP               = Keys.VK_PRIOR;
Keys.VK_PAGEDOWN             = Keys.VK_NEXT;
Keys.VK_BACKSPACE            = Keys.VK_BACK;

Keys.VK_MUTE                 = Keys.VK_VOLUME_MUTE;
Keys.VK_VOLUMEDOWN           = Keys.VK_VOLUME_DOWN;
Keys.VK_VOLUMEUP             = Keys.VK_VOLUME_UP;

Keys.VK_NEXTTRACK            = Keys.VK_MEDIA_NEXT_TRACK;
Keys.VK_PREVTRACK            = Keys.VK_MEDIA_PREV_TRACK;
Keys.VK_PREVIOUSTRACK        = Keys.VK_MEDIA_PREV_TRACK;
Keys.VK_PLAYPAUSE            = Keys.VK_MEDIA_PLAY_PAUSE;
Keys.VK_STOP                 = Keys.VK_MEDIA_STOP;

Keys.VK_CLEAR                = Keys.VK_BROWSER_BACK; // MC code

// Using 0x97 - 0x9F unassigned range
Keys.VK_RED                  = 0x97;
Keys.VK_GREEN                = 0x98;
Keys.VK_YELLOW               = 0x99;
Keys.VK_BLUE                 = 0x9A;
Keys.VK_GUIDE                = 0x9B;
Keys.VK_INFO                 = 0x9C; // MC code

Keys.VK_REWIND               = 0x9D;
Keys.VK_FASTFORWARD          = 0x9E;
Keys.VK_START                = 0x9F;

// Using 0xC1 - 0xD7 reserved range
Keys.VK_CHANNELUP            = 0xC1;
Keys.VK_CHANNELDOWN          = 0xC2;
Keys.VK_RECORD               = 0xC3;



Keys.parseKey = function(keyName)
{
	if(!keyName)
		return 0;

	var keyNameU = keyName.toUpperCase();
	
	var key = 0;
	if(keyNameU.substr(0, 3) == "VK_")
		key = Keys[keyNameU];
	else
		key = Keys["VK_" + keyNameU];
	
	if(typeof(key) == "number")
		return key;

	key = parseInt(keyName);
	if(isNaN(key)) key = 0;

	return key;
}
Type.registerType('Keys');


//**********************************************************
// UI
//**********************************************************
function $(elementId)
{
	return document.getElementById(elementId);
}
